package org.ucas.faker.web.utils.mql.domain;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.ucas.faker.web.utils.model.Documentable;
import org.ucas.faker.web.utils.mql.exception.MqlParseException;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;

public class Condition extends Where {

  private String name;
  private OPEAR opear;
  private Object value;

  public Condition(CONN conn, String name, String opear, Object value) throws MqlParseException {
   this(conn,name,OPEAR.fromString(opear),value);
  }

  public Condition(CONN conn, String name, OPEAR opear, Object value) {
    super(conn);
    this.name = name;
    this.opear = opear;
    this.value = value;
  }

  public Condition(String name, OPEAR opear, Object value) {
    this(CONN.AND, name, opear, value);
  }

  public Condition(String name, String opear, Object value) throws MqlParseException {
    this(CONN.AND, name, OPEAR.fromString(opear), value);
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Object getValue() {
    return value;
  }

  public void setValue(Object value) {
    this.value = value;
  }

  public OPEAR getOpear() {
    return opear;
  }

  public void setOpear(OPEAR opear) {
    this.opear = opear;
  }

  @Override
  public float genScore(Documentable document) {
    float score = this.getBoost() == null? 1.0f : boost;
    switch (opear) {
      case CONTAINS:
        return doSatisfy(document, new Predicate<Object>() {
          @Override
          public boolean test(Object o) {
            return !(o == null || !(o instanceof String)) && ((String) o).contains((String) value);
          }
        })? score : 0.0f;
      default:
        return 0.0f;
    }
  }

  @Override
  public float notScore(Documentable document) {
    float score = this.getBoost() == null ? 1.0f : boost;
    return genScore(document) > 0.0f ? 0.0f : score;
  }

  private boolean doSatisfy(Documentable document, Predicate<Object> predicate) {
    if(name.toUpperCase().equals("ANY")) {
      for(String fieldName :document.fieldNames())
        if(predicate.test(document.getField(fieldName))) return true;
      return false;
    } else if(name.toUpperCase().equals("ALL")){
      for(String fieldName :document.fieldNames())
        if(!predicate.test(document.getField(fieldName))) return false;
      return true;
    }
    else return predicate.test(document.getField(name));
  }

  public String toString() {
    String result = "";

    if (value instanceof Object[]) {
      result += this.conn + " " + this.name + " " + this.opear + " " + Arrays.toString((Object[]) value);
    } else {
      result += this.conn + " " + this.name + " " + this.opear + " " + this.value;
    }
    return result;
  }

  public enum OPEAR {
    LIKE, MATCH, CONTAINS;
    public static Map<String, OPEAR> methodNameToOpear;

    private static BiMap<OPEAR, OPEAR> negatives;

    static {
      methodNameToOpear = new HashMap<>();
    }

    static {
      negatives = HashBiMap.create(7);
    }

    public static OPEAR fromString(String op) throws MqlParseException {
      switch (op) {
        case "like":
          return OPEAR.LIKE;
        case "match":
          return OPEAR.MATCH;
        case "contains":
          return OPEAR.CONTAINS;
        default:
          throw new MqlParseException(op + " is err!");
      }
    }

    public OPEAR negative() throws MqlParseException {
      OPEAR negative = negatives.get(this);
      negative = negative != null ? negative : negatives.inverse().get(this);
      if (negative == null) {
        throw new MqlParseException("OPEAR negative not supported: " + this);
      }
      return negative;
    }
  }
}
